package pers.cz.postgirl.core;

import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import pers.cz.postgirl.config.SystemConfig;
import pers.cz.postgirl.core.common.ParamFilter;
import pers.cz.postgirl.util.JefLog;

import java.util.HashMap;
import java.util.Map;

/**
 * @program: postgirl-client
 * @description: javassist事件监听实现,主要负责在指定地方插码
 * @author: Cheng Zhi
 * @create: 2023-05-28 15:45
 **/
public class AssistEventListener implements EventListener {

    private JefLog log = new JefLog(AssistEventListener.class);
    @Override
    public void onEvent(Event event, Object... objects) throws Throwable {

        if (objects.length == 0 || !(objects[0] instanceof CtClass)) {
            log.error("If you use javaAssist to be enhance, you must specify CtClass");
        }

        CtClass ctClass = (CtClass) objects[0];
        // 执行对应的策略
        switch (event.evenType) {
            case BEFORE :
                onBefore((BeforeEvent) event, ctClass);
                break;
            case RETURN:
                onReturn((ReturnEvent) event, ctClass);
                break;
            case THROW:
                onThrowable(ctClass);
                break;
            case AFTER:
                onAfter(ctClass);
                break;
        }
    }

    /**
     * 处理方法入口处插码
     * @param event    事件类型
     * @param ctClass  目标类
     * @throws Exception
     */
    private void onBefore(BeforeEvent event,CtClass ctClass) throws Exception {
        try {
            CtMethod declaredMethod = ctClass.getDeclaredMethod(event.getJavaMethodName());
            EnhanceModel.MethodPattern.DefaultCodePattern javaMethodContent = event.getJavaMethodContent();

            if (javaMethodContent != null && javaMethodContent.getBeforeCode() != null) {
                Map<String, Object> paramterMap = new HashMap<String, Object>();
                paramterMap.put("CTMETHOD", declaredMethod);
                paramterMap.put("CTCLASSNAME", ctClass.getName());
                javaMethodContent.getBeforeCode().setParamters(paramterMap);
            }
            String src = javaMethodContent == null ? "" : javaMethodContent.getBeforeCode().execute();

            // 参数个数一致才处理
            if (declaredMethod.getParameterTypes().length != event.getParamTypes().length) {
                return;
            }

            if (src == null || src.isEmpty()) {
                src = getDefaultBeforeCode(declaredMethod, ctClass.getName());
            }
            if (SystemConfig.getInstance().isDebug()) {
                log.info("will insert before code is:" + src);
            }
            declaredMethod.insertBefore(src);
        } catch (NotFoundException | CannotCompileException e) {
            e.printStackTrace();
            log.error("enhance fail!!!  method is not found");
        }
    }

    /**
     * 方法返回前插码
     */
    private void onReturn(ReturnEvent event, CtClass ctClass) {
        try {
            CtMethod declaredMethod = ctClass.getDeclaredMethod(event.getJavaMethodName());
            EnhanceModel.MethodPattern.DefaultCodePattern javaMethoContent = event.getJavaMethoContent();

            if (javaMethoContent != null && javaMethoContent.getReturnCode() != null) {
                Map<String, Object> paramterMap = new HashMap<String, Object>();
                paramterMap.put("CTMETHOD", declaredMethod);
                paramterMap.put("CTCLASSNAME", ctClass.getName());
                javaMethoContent.getReturnCode().setParamters(paramterMap);
            }
            String src = javaMethoContent == null? "" : javaMethoContent.getReturnCode().execute();
            // 参数个数一致才处理
            if (declaredMethod.getParameterTypes().length != event.getParamTypes().length) {
                return;
            }
            if (src == null || src.isEmpty()) {
                src = getDefaultReturnCode(declaredMethod, ctClass.getName());
            }
            if (SystemConfig.getInstance().isDebug()) {
                log.info("will insert after code is: " + src);
            }
            declaredMethod.insertAfter(src);
        } catch (NotFoundException | CannotCompileException e) {
            e.printStackTrace();
            log.error("enhance fail!!!  method is not found");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void onThrowable(CtClass ctClass) {
        System.out.println("方法执行异常插码");
    }

    private void onAfter(CtClass ctClass) {
        System.out.println("方法执行后插码");
    }

    /**
     * 默认方法入口插码内容
     * @param ctMethod     目标方法
     * @param ctClassName  目标类名
     * @return
     * @throws NotFoundException
     */
    private String getDefaultBeforeCode(CtMethod ctMethod, String ctClassName) throws NotFoundException {

        StringBuilder sb = new StringBuilder();
        sb.append("try {\n");
        sb.append("          pers.cz.postgirl.core.Spy spy = new pers.cz.postgirl.core.Spy();\n");
        sb.append("          spy.setStartTime(System.currentTimeMillis());\n");
        sb.append("          spy.setMethodName(");
        sb.append(getString(ctMethod.getName()) + ");\n");
        sb.append("          spy.setClzName(");
        sb.append(getString(ctClassName) + ");\n");
        sb.append(createDumpParams(ctMethod.getParameterTypes()));
        sb.append("          spy.setDataType(pers.cz.postgirl.core.Spy.DataType.INPUT);\n");

        // 判断是否需要回放，如果需要回放，则直接去匹配
        sb.append("          if(pers.cz.postgirl.core.SpyHandler.judgePlayback()) {\n");
        sb.append("              pers.cz.postgirl.core.Spy myResult$ = pers.cz.postgirl.core.SpyHandler.getResult(spy);\n");
        sb.append(transReturn(ctMethod.getReturnType().getName()));
        sb.append("          }\n");
        sb.append("          pers.cz.postgirl.core.SpyHandler.onHandle(spy);\n");
        sb.append("        } catch (Exception e) {\n");
        sb.append("            e.printStackTrace();\n");
        sb.append( "        }");

        return sb.toString();
    }

    private String transReturn(String returnType) {
        if("int".equals(returnType)){
            return "              return Integer.parseInt(myResult$.getReturnValue().toString());\n";
        }else if("long".equals(returnType)){
            return "              return Long.parseLong(myResult$.getReturnValue().toString());\n";
        }else if("float".equals(returnType)){
            return "              return Float.parseFloat(myResult$.getReturnValue().toString());\n";
        }else if("double".equals(returnType)){
            return "              return Double.parseDouble(myResult$.getReturnValue().toString());\n";
        }else if("byte".equals(returnType)){
            return "              return Byte.parseByte(myResult$.getReturnValue().toString());\n";
        }else if("short".equals(returnType)){
            return "              return Short.parseShort(myResult$.getReturnValue().toString());\n";
        }else if("char".equals(returnType)){
            return "              return myResult$.getReturnValue().toString().charAt(0);\n";
        }else if("boolean".equals(returnType)){
            return "              return Boolean.parseBoolean(myResult$.getReturnValue().toString());\n";
        }else if("void".equals(returnType)) {
            return "              return;\n";
        } else if("java.lang.Object".equals(returnType)) {
            return  "              return myResult$.getReturnValue();\n";
        } else{
            return "              return ("+returnType+")myResult$.getReturnValue();\n";
        }
    }

    private String createDumpParams(CtClass[] parameterTypes) {
        StringBuilder sb = new StringBuilder();
        if (parameterTypes != null && parameterTypes.length > 0) {
            for (int i = 0; i < parameterTypes.length; i++) {

                if (!ParamFilter.isNeedfilterParam(parameterTypes[i].getName())) {
                    sb.append("          spy.addParam(\"");
                    sb.append(parameterTypes[i].getName());
                    sb.append("\",$");
                    sb.append(i + 1);
                    sb.append(");\n");
                }
            }
        }
        return sb.toString();
    }

    /**
     * 默认方法返回前的插码内容
     * @param ctMethod    目标方法
     * @param ctClassName 目标类
     * @return
     */
    private String getDefaultReturnCode(CtMethod ctMethod, String ctClassName)  {
        StringBuilder sb = new StringBuilder();
        sb.append("try{\n");
        sb.append("     pers.cz.postgirl.core.Spy spy = new pers.cz.postgirl.core.Spy();\n");
        sb.append("     spy.setClzName(" + getString(ctMethod.getDeclaringClass().getName()) + ");\n");
        sb.append("     spy.setMethodName(" + getString(ctMethod.getName()) + ");\n");
        sb.append("     spy.setEndTime(System.currentTimeMillis());\n");
        sb.append("     spy.setDataType(pers.cz.postgirl.core.Spy.DataType.OUTPUT);\n");
       // sb.append("     spy.setRealTypeName($_.getClass().getName());\n");
        String rtnTypeName = null;
        try {
            rtnTypeName = ctMethod.getReturnType().getName();
        }catch (NotFoundException e){
            e.printStackTrace();
        }
        sb.append(createDumpReturnValue(rtnTypeName));
        sb.append("     pers.cz.postgirl.core.SpyHandler.onHandle(spy);\n");
        sb.append("}catch(Throwable a){\n");
        sb.append("     a.printStackTrace();\n");
        sb.append("}\n");
        return sb.toString();
    }

    /**
     * 创建出参的插码
     * @param returnType    返回值类型
     * @return  插码结果
     */
    private String createDumpReturnValue(String returnType) {
        StringBuilder sb = new StringBuilder();
        if ("void".equals(returnType)) {
            sb.append("spy.putReturnValue(");
            sb.append(getString(returnType));
            sb.append(",").append(getString("")).append(");\n");
        } else {
            sb.append("spy.putReturnValue(")
                    .append(getString(returnType))
                    .append(",$_);\n");
        }
        return sb.toString();
    }

    private String getString(String data) {
        return "\"" + data + "\"";
    }
}
